{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bd34b580",
   "metadata": {},
   "source": [
    "# DAY 31"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d32a01ee",
   "metadata": {},
   "source": [
    "昨天我们已经介绍了如何在不同的文件中，导入其他目录的文件，核心在于了解导入方式和python解释器检索目录的方式。\n",
    "\n",
    "搞清楚了这些，那我们就可以来看看，如何把一个文件，拆分成多个具有着独立功能的文件，然后通过import的方式，来调用这些文件。这样具有几个好处：\n",
    "1. 可以让项目文件变得更加规范和清晰\n",
    "2. 可以让项目文件更加容易维护，修改某一个功能的时候，只需要修改一个文件，而不需要修改多个文件。\n",
    "3. 文件变得更容易复用，部分通用的文件可以单独拿出来，进行其他项目的复用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3445f044",
   "metadata": {},
   "source": [
    "## 机器学习项目的流程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2a5bf59",
   "metadata": {},
   "source": [
    "一个典型的机器学习项目通常包含以下阶段：\n",
    "- **数据加载**：从文件、数据库、API 等获取原始数据。\n",
    "    - 命名参考：`load_data.py` 、`data_loader.py`\n",
    "- **数据探索与可视化**：了解数据特性，初期可用 Jupyter Notebook，成熟后固化绘图函数。\n",
    "    - 命名参考：`eda.py` 、`visualization_utils.py` \n",
    "- **数据预处理**：处理缺失值、异常值，进行标准化、归一化、编码等操作。 \n",
    "    - 命名参考：`preprocess.py` 、`data_cleaning.py` 、`data_transformation.py` \n",
    "- **特征工程**：创建新特征，选择、优化现有特征。\n",
    "    - 命名参考：`feature_engineering.py` \n",
    "- **模型训练**：构建模型架构，设置超参数并训练，保存模型。\n",
    "    - 命名参考：`model.py` 、`train.py` \n",
    "- **模型评估**：用合适指标评估模型在测试集上的性能，生成报告。\n",
    "    - 命名参考：`evaluate.py` \n",
    "- **模型预测**：用训练好的模型对新数据预测。\n",
    "    - 命名参考：`predict.py` 、`inference.py` "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d23445dd",
   "metadata": {},
   "source": [
    "## 文件的组织"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa974893",
   "metadata": {},
   "source": [
    "### 1. 项目核心代码组织\n",
    "- **src/（source的缩写）**：存放项目的核心源代码。按照机器学习项目阶段进一步细分：\n",
    "    - **src/data/**：放置与数据相关的代码。\n",
    "        - `src/data/load_data.py`：负责从各类数据源（如文件系统、数据库、API 等）读取原始数据。\n",
    "        - `src/data/preprocess.py`：进行数据清洗（处理缺失值、异常值）、数据转换（标准化、归一化、编码等）操作。\n",
    "        - `src/data/feature_engineering.py`：根据业务和数据特点，创建新特征或对现有特征进行选择、优化。\n",
    "    - **src/models/**：关于模型的代码。\n",
    "        - `src/models/model.py`：定义模型架构，比如神经网络结构、机器学习算法模型设定等。\n",
    "        - `src/models/train.py`：设置模型超参数，并执行训练过程，保存训练好的模型。\n",
    "        - `src/models/evaluate.py`：使用合适的评估指标（如准确率、召回率、均方误差等），在测试集上评估模型性能，生成评估报告。\n",
    "        - `src/models/predict.py` 或 `src/models/inference.py`：利用训练好的模型对新数据进行预测。\n",
    "    - **src/utils/**：存放通用辅助函数代码，可进一步细分：\n",
    "        - `src/utils/io_utils.py`：包含文件读写相关帮助函数，比如读取特定格式文件、保存数据到文件等。\n",
    "        - `src/utils/logging_utils.py`：实现日志记录功能，方便记录项目运行过程中的信息，便于调试和监控。\n",
    "        - `src/utils/math_utils.py`：特定的数值计算函数，像自定义的矩阵运算、统计计算等。\n",
    "        - `src/utils/plotting_utils.py`：绘图工具函数，用于生成数据可视化图表（如绘制损失函数变化曲线、特征分布直方图等 ）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23f3f73c",
   "metadata": {},
   "source": [
    "### 2. 配置文件管理\n",
    "- **config/ 目录**：集中存放项目的配置文件，方便管理和切换不同环境（开发、测试、生产）的配置。\n",
    "    - `config/config.py` 或 `config/settings.py`：以 Python 代码形式定义配置参数。\n",
    "    - `config/config.yaml` 或 `config/config.json`：采用 YAML 或 JSON 格式，清晰列出文件路径、模型超参数、随机种子、API 密钥等可配置参数。\n",
    "    - `.env` 文件：通常放在项目根目录，用于存储敏感信息（如数据库密码、API 密钥等），在代码中通过环境变量的方式读取，一般会被 `.gitignore` 忽略，防止敏感信息泄露。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "604f3218",
   "metadata": {},
   "source": [
    "### 3. 实验与探索代码\n",
    "- **notebooks/ 或 experiments/ 目录**：用于初期的数据探索、快速实验、模型原型验证。\n",
    "    - `notebooks/initial_eda.ipynb`：在项目初期，使用 Jupyter Notebook 进行数据探索与可视化，了解数据特性，分析数据分布、相关性等。\n",
    "    - `experiments/model_experimentation.py`：编写脚本对不同模型架构、超参数组合进行快速实验，对比实验结果，寻找最优模型设置。\n",
    "  \n",
    "  这部分往往是最开始的探索阶段，后面跑通了后拆分成了完整的项目，留作纪念用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c743a1db",
   "metadata": {},
   "source": [
    "### 4. 项目产出物管理\n",
    "- **data/ 目录**：存放项目相关数据。\n",
    "    - `data/raw/`：放置从外部获取的未经处理的原始数据，保持数据原始状态。\n",
    "    - `data/processed/`：存放经过预处理（清洗、转换、特征工程等操作）后的数据，供模型训练和评估使用。\n",
    "    - `data/interim/`：（可选）保存中间处理结果，比如数据清洗过程中生成的临时文件、特征工程中间步骤产生的数据等。\n",
    "- **models/ 目录**：专门存放训练好的模型文件，根据模型保存格式不同，可能是 `.pkl`（Python  pickle 格式，常用于保存 sklearn 模型 ）、`.h5`（常用于保存 Keras 模型 ）、`.joblib` 等。\n",
    "- **reports/ 或 output/ 目录**：存储项目运行产生的各类报告和输出文件。\n",
    "    - `reports/evaluation_report.txt`：记录模型评估的详细结果，包括各项评估指标数值、模型性能分析等。\n",
    "    - `reports/visualizations/`：存放数据可视化图片，如损失函数收敛图、预测结果对比图等。\n",
    "    - `output/logs/`：保存项目运行日志文件，记录项目从开始到结束过程中的关键信息，如训练开始时间、训练过程中的损失值变化、预测时间等。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a49a727",
   "metadata": {},
   "source": [
    "**总结一下通用的拆分起步思路：**\n",
    "\n",
    "1.  **首先，按照机器学习的主要工作流程（数据处理、训练、评估等）将代码分离到不同的 `.py` 文件中。** 这是最基本也是最有价值的一步。\n",
    "2.  **然后，创建一个 `utils.py` 来存放通用的辅助函数。**\n",
    "3.  **考虑将所有配置参数集中到一个 `config.py` 文件中。**\n",
    "4.  **为你的数据和模型产出物创建专门的顶层目录，如 `data/` 和 `models/`，将它们与你的源代码（通常放在 `src/` 目录）分开。**\n",
    "\n",
    "当遵循这些通用的拆分思路和原则时，项目结构自然会变得清晰。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c560568d",
   "metadata": {},
   "source": [
    "## 注意事项"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "287cf334",
   "metadata": {},
   "source": [
    "### if __name__ == \"__main__\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11afe40e",
   "metadata": {},
   "source": [
    "常常会看到if __name__ == \"__main__\"这个写法，实际上，每个文件都是一个对象，对象就会有属性和方法。\n",
    "\n",
    "如果直接运行这个文件，则__name__等于__main__，若这个文件被其他模块导入，则__name__不等于__main__。\n",
    "\n",
    "这个写法有如下好处：\n",
    "1. 明确程序起点：一个 Python 项目往往由多个模块组成。if __name__ == \"__main__\" 可清晰界定程序执行的起始位置。比如一个包含数据处理模块 data_processing.py、模型训练模块 model_training.py 的机器学习项目，在 model_training.py 中用 if __name__ == \"__main__\" 包裹训练相关的主逻辑代码，运行该文件时就知道需要从这里开始执行（其他文件都是附属文件），让项目结构和执行流程更清晰。（大多时候如此）\n",
    "\n",
    "2. 避免执行：python遵从模块导入即执行机制，当你使用 import xxx 导入一个模块时，Python 会执行该模块中的所有顶层代码（即不在任何函数或类内部的代码）。如果顶层代码中定义了全局变量或执行了某些操作（如读取文件、初始化数据库连接），这些操作会在导入时立即生效，并可能影响整个程序的状态。为了避免执行不必要的代码，我们可以使用 if __name__ == \"__main__\" 来避免在导入时执行不必要的代码。这样，只有当模块被直接运行时（即被执行 python xxx.py），才会执行顶层代码，而导入时则不会执行。这样，我们就可以确保在导入模块时，不会执行不必要的代码，从而提高程序的性能和可维护性。\n",
    "   \n",
    "3. 合理的资源管理：if __name__ == \"__main__\" 与定义 main 函数结合使用，函数内变量在函数执行完这些变量被释放，能及时回收内存资源，避免内存泄漏，保证程序高效运行。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "caf0c43f",
   "metadata": {},
   "source": [
    "### 编码格式"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "436f3288",
   "metadata": {},
   "source": [
    "规范的py文件，首行会有：# -*- coding: utf-8 -*-\n",
    "\n",
    "主要目的是 显式声明文件的编码格式，确保 Python 解释器能正确读取和解析文件中的非 ASCII 字符（如中文、日文、特殊符号等）。也就是说这个是写给解释器看的。\n",
    "\n",
    "\n",
    "因为，在 Python 2.x 时代，默认编码是 ASCII，不支持直接在代码中写入非 ASCII 字符（如中文注释、字符串中的中文），否则会报错（SyntaxError: Non-UTF-8 code starting with...）。但是Python 3.x 默认为 UTF-8 编码，理论上可以省略编码声明。但实际开发中，为了兼容旧代码、明确文件编码规则，或在团队协作中避免因编辑器 / 环境配置不同导致的乱码问题，许多开发者仍会保留这一行声明。\n",
    "\n",
    "ps：\n",
    "1. 编码声明必须出现在文件的前两行（通常是首行），否则会被忽略。\n",
    "2. 如果编码格式没问题，可能是vscode的编码格式不是utf-8，可以尝试修改编码格式。\n",
    "3. 常见的编码报错是因为字符串编码问题，可以尝试显式转化，即读取的时候转化为utf-8编码。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd86558e",
   "metadata": {},
   "source": [
    "非 ASCII 字符的代码如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a0cffce9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好，世界！\n"
     ]
    }
   ],
   "source": [
    "# -*- coding: utf-8 -*-\n",
    "msg = \"你好，世界！\"  # 中文字符串\n",
    "print(msg)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0848438c",
   "metadata": {},
   "source": [
    "很多时候，项目中会包含gitattribute文件，来确保在不同操作系统和编辑器中，文件的编码格式一致。这里我们后面说到git工具在介绍"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84806d2c",
   "metadata": {},
   "source": [
    "### 类型注解"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2cd906c9",
   "metadata": {},
   "source": [
    "Python 的类型注解是在 Python 3.5+ 引入的特性，用于为变量、函数参数、返回值和类属性等添加类型信息。虽然 Python 仍是动态类型语言，但类型注解可以提高代码可读性、可维护性，并支持静态类型检查工具（如 mypy）。\n",
    "\n",
    "其次你在安装python插件的时候，附带安装了2个插件\n",
    "1. 一个是python debugger用于断点调试，我们已经介绍了\n",
    "2. 另一个是pylance，用于代码提示和类型检查，这个插件会根据你的代码中的类型注解，给出相应的提示和检查，比如你定义了一个函数，参数类型是int，那么当你传入一个字符串时，它会提示你传入的参数类型不正确。\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f543dcba",
   "metadata": {},
   "source": [
    "变量类型注解语法为 变量名: 类型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a59ec439",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 变量的类型注解\n",
    "name: str = \"Alice\"\n",
    "age: int = 30\n",
    "height: float = 1.75\n",
    "is_student: bool = False"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ce6247a",
   "metadata": {},
   "source": [
    "函数类型注解为函数参数和返回值指定类型，语法为 def 函数名(参数: 类型) -> 返回类型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "883d4c04",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(a: int, b: int) -> int:\n",
    "    return a + b\n",
    "\n",
    "def greet(name: str) -> None:\n",
    "    print(f\"Hello, {name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8a4af5f",
   "metadata": {},
   "source": [
    "类属性与方法的类型注解：为类的属性和方法添加类型信息。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7f2f9755",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义一个矩形类\n",
    "class Rectangle:\n",
    "    width: float      # 矩形宽度（浮点数），类属性的类型注解（不初始化值）\n",
    "    height: float     # 矩形高度（浮点数）\n",
    "\n",
    "    def __init__(self, width: float, height: float):\n",
    "        self.width = width\n",
    "        self.height = height\n",
    "\n",
    "    def area(self) -> float:\n",
    "        # 计算面积（宽度 × 高度）\n",
    "        return self.width * self.height"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42895205",
   "metadata": {},
   "source": [
    "上述的```width: float      # 矩形宽度（浮点数）```这个写法由于没有对变量赋值，所以是一种类型注解写法"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "vs",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
